module Account
    require 'digest/sha1'
    
    # mix this class into a user model to get login functionality
    module AccountModel
        
        def self.included(base)
            base.class_eval do
                
                before_create :make_activation_code
                
                # use the table name given
                set_table_name AccountConfiguration.config(:user_table)
                
                validates_presence_of :login
                validates_length_of :login, :within => 3..40
                validates_uniqueness_of :login
                
                validates_uniqueness_of :email
                validates_format_of :email, :with => /^[^@]+@.+$/
                
                validates_presence_of :password
                validates_confirmation_of :password
                validates_length_of :password, { :minimum => 5 }
                validates_length_of :password, { :maximum => 40 }
                
                attr_accessor :password_confirmation                
                
            end
            base.extend(ClassMethods)
        end
        
        module ClassMethods
            
            # authenticate the user via login and password
            def authenticate(login, password)
                user = find(:first, :conditions => ["login = ? AND activated_at IS NOT NULL AND deleted = 0", login])
                return nil if user.nil?
                logged_in_user = find(:first, :conditions => ["login = ? AND salted_password = ? AND activated_at IS NOT NULL", login, user.salt_the_password(user.salt, user.hashed(password))])
                return nil if logged_in_user.nil?
                logged_in_user.logged_in_at = Time.now.utc
                logged_in_user.save(false) # don't validate.
                logged_in_user
            end
            
            # authenticate the user via security token TODO finish working out how to do this
            def authenticate_by_api_key(key)
                logged_in_user = find(:first, :conditions => ["api_key = ?", key])
                return nil if logged_in_user.nil?
                logged_in_user.logged_in_at = Time.now.utc
                logged_in_user.save(false) # don't validate.
                logged_in_user
            end
            
        end    
        
        public
        
        # 'password' is a virtual attribute.  Do all password validation here.
        def password
            @password
        end
        
        def password=(password_value)
            @password = password_value
            self.salt = make_salt
            self.salted_password = self.salt_the_password(salt, self.hashed(@password))
        end
        
        # Activates the user in the database.
        def activate
            @activated = true
            self.activated_at = Time.now.utc
            self.activation_code = nil
            self.save(false) # don't validate.
        end
        
        # Returns true if the user has just been activated.
        def recently_activated?
            @activated
        end
        
        # determine if the security token has expired
        def security_token_expired?
            self.security_token and self.token_expiry and (Time.now > self.token_expiry)
        end
        
        # add more time onto security expiration.  TODO may want to change the token
        def update_security_expiration
            self.token_expiry = token_lifetime
            self.save(false) # don't validate.
        end
        
        def change_password(password, confirm = nil)
            self.password = password
            self.password_confirmation = confirm.nil? ? password : confirm
            self.save
        end
        
        def remember_token?
            remember_token_expires_at && Time.now.utc < remember_token_expires_at 
        end
        
        # These create and unset the fields required for remembering users between browser closes
        def remember_me
            self.remember_token_expires_at = 2.weeks.from_now.utc
            self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
            save(false)
        end
        
        def forget_me
            self.remember_token_expires_at = nil
            self.remember_token = nil
            save(false)
        end
        
        def hashed(hash_it)
            # check if a salt has been set...
            if AccountConfiguration.config(:salt) == nil
                raise "You must define a :salt value in the configuration for the AccountConfiguration module."
            end
            
            return Digest::SHA1.hexdigest("#{AccountConfiguration.config(:salt)}--#{hash_it}--}")[0..39]
        end
        
        def make_salt
            self.hashed("salt-#{Time.now}")
        end
        
        def salt_the_password(salt, hashed_password)
            hashed(salt + hashed_password)
        end
        
        def reset_password
            # First update the password_reset_code before setting the 
            # reset_password flag to avoid duplicate email notifications.
            update_attributes(:password_reset_code => nil)
            @reset_password = true
        end
        
        def recently_reset_password?
            @reset_password
        end
        
        def forgot_password
            @forgotten_password = true
            reset_code = self.make_password_reset_code
            self.update_attribute(:password_reset_code, reset_code)
            return reset_code
        end
        
        def recently_forgot_password?
            @forgotten_password
        end
        
        def generate_api_key
            self.api_key = self.hashed("key-#{Time.now}" + rand.to_s)
            self.update_attribute(:api_key, self.api_key)
            return self.api_key
        end
        
        protected
        
        def make_password_reset_code
            self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
        end

        # store the amount of time that the security token is valid for
        def token_lifetime
            Time.at(Time.now.to_i + AccountConfiguration.config(:security_token_life_days) * 60 * 60 * 24)
        end
        
        # generate a token that will let the user login via a url
        # TODO work this out so that the token the user passes in to login
        # is the unhashed version of a token.  We will then hash that value
        # and compare it to the hash in our db
        def generate_security_token
            self.security_token = self.hashed(self.salted_password + Time.now.to_i.to_s + rand.to_s)[0..39]
            self.token_expiry = token_lifetime
            return self.security_token
        end
        
        # create an activation code that can be emailed to a user
        def make_activation_code
            self.activation_code = self.hashed(Time.now.to_i.to_s + rand.to_s)[0..39]
        end
        
    end
end